// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: donggu, jhkim

#pragma once

#include <memory>
#include <string>

#include <pybind11/pybind11.h>

#include "appcoin2/support/py_module/driver/py_book_trade.h"
#include "coin2/base/feed_convert_fastfeature.h"
#include "coin2/exchange/base/feed/update.h"
#include "fastfeature/feature_feed.pb.h"

namespace py = pybind11;

namespace coin2::exchange::driver {
namespace impl {

using coin::proto::ExchangeType_Name;
using coin::proto::MarketType_Name;
using coin::proto::OrderDuration_Name;
using coin::proto::OrderSide_Name;
using coin::proto::OrderState_Name;
using coin::proto::OrderType_Name;

using coin2::exchange::base::feed::FeedUpdate;

class PyLiquidationView {
 public:
  explicit PyLiquidationView(const FeedUpdate* upd) : upd_(upd), timestamp_(upd->timestamp()) {}

  // TODO(daniel): move to base class
  auto timestamp() const -> int64_t { return timestamp_; }
  auto exchange() const -> int { return static_cast<int>(upd_->exchange()); }
  auto exchange_str() const -> std::string { return ExchangeType_Name(exchange()); }
  auto market() const -> int { return static_cast<int>(upd_->market()); }
  auto market_str() const -> std::string { return MarketType_Name(market()); }
  auto mea() const -> std::string { return upd_->mea().String(); }
  auto relative_norm() const -> const std::string& { return upd_->product().relative_norm(); }
  auto absolute_norm() const -> const std::string& { return upd_->product().absolute_norm(); }
  auto feed_native_symbol() const -> const std::string& {
    return upd_->product().feed_native_symbol();
  }

  auto order_price() const -> double { return upd_->liquidation_order().price; }
  auto order_qty() const -> double { return upd_->liquidation_order().qty; }
  auto avg_price() const -> double { return upd_->liquidation_order().avg_price; }
  auto fill_qty() const -> double { return upd_->liquidation_order().fill_qty; }
  auto order_type() const -> std::string {
    return OrderType_Name(upd_->liquidation_order().order_type);
  }
  auto order_duration() const -> std::string {
    return OrderDuration_Name(upd_->liquidation_order().order_duration);
  }
  auto order_side() const -> std::string {
    return OrderSide_Name(upd_->liquidation_order().order_side);
  }
  auto order_state() const -> std::string {
    return OrderState_Name(upd_->liquidation_order().order_state);
  }
  auto order_id() const -> std::string { return upd_->liquidation_order().order_id; }

  auto as_dict() -> py::dict {
    using namespace coin2::exchange::feed;
    using namespace pybind11::literals;
    return py::dict(
        "type"_a = FeedType_Name(upd_->feed_type()),
        "timestamp"_a = timestamp(),
        "market"_a = market_str(),
        "exchange"_a = exchange_str(),
        "mea"_a = mea(),
        "relative_norm"_a = relative_norm(),
        "absolute_norm"_a = absolute_norm(),
        "price"_a = order_price(),
        "qty"_a = order_qty(),
        "avg_price"_a = avg_price(),
        "fill_qty"_a = fill_qty(),
        "order_type"_a = order_type(),
        "order_side"_a = order_side(),
        "order_duration"_a = order_duration(),
        "order_state"_a = order_state(),
        "order_id"_a = order_id());
  }

 private:
  const FeedUpdate* upd_;
  int64_t timestamp_ = 0;
};

class PyOpenInterestView {
 public:
  explicit PyOpenInterestView(const FeedUpdate* upd) : upd_(upd), timestamp_(upd->timestamp()) {}

  // TODO(daniel): move to base class
  auto timestamp() const -> int64_t { return timestamp_; }
  auto exchange() const -> int { return static_cast<int>(upd_->exchange()); }
  auto exchange_str() const -> std::string { return ExchangeType_Name(exchange()); }
  auto market() const -> int { return static_cast<int>(upd_->market()); }
  auto market_str() const -> std::string { return MarketType_Name(market()); }
  auto mea() const -> std::string { return upd_->mea().String(); }
  auto relative_norm() const -> const std::string& { return upd_->product().relative_norm(); }
  auto absolute_norm() const -> const std::string& { return upd_->product().absolute_norm(); }
  auto feed_native_symbol() const -> const std::string& {
    return upd_->product().feed_native_symbol();
  }

  auto open_interest_qty() const -> double { return upd_->open_interest().open_interest_qty; }

  auto as_dict() -> py::dict {
    using namespace coin2::exchange::feed;
    using namespace pybind11::literals;
    return py::dict(
        "type"_a = FeedType_Name(upd_->feed_type()),
        "timestamp"_a = timestamp(),
        "market"_a = market_str(),
        "exchange"_a = exchange_str(),
        "mea"_a = mea(),
        "relative_norm"_a = relative_norm(),
        "absolute_norm"_a = absolute_norm(),
        "open_interest_qty"_a = open_interest_qty());
  }

 private:
  const FeedUpdate* upd_;
  int64_t timestamp_ = 0;
};

class PyMarkPriceView {
 public:
  explicit PyMarkPriceView(const FeedUpdate* upd) : upd_(upd), timestamp_(upd->timestamp()) {}

  // TODO(daniel): move to base class
  auto timestamp() const -> int64_t { return timestamp_; }
  auto exchange() const -> int { return static_cast<int>(upd_->exchange()); }
  auto exchange_str() const -> std::string { return ExchangeType_Name(exchange()); }
  auto market() const -> int { return static_cast<int>(upd_->market()); }
  auto market_str() const -> std::string { return MarketType_Name(market()); }
  auto mea() const -> std::string { return upd_->mea().String(); }
  auto relative_norm() const -> const std::string& { return upd_->product().relative_norm(); }
  auto absolute_norm() const -> const std::string& { return upd_->product().absolute_norm(); }
  auto feed_native_symbol() const -> const std::string& {
    return upd_->product().feed_native_symbol();
  }

  auto mark_price() const -> double { return upd_->mark_price().mark_price; }

  auto as_dict() -> py::dict {
    using namespace coin2::exchange::feed;
    using namespace pybind11::literals;
    return py::dict(
        "type"_a = FeedType_Name(upd_->feed_type()),
        "timestamp"_a = timestamp(),
        "market"_a = market_str(),
        "exchange"_a = exchange_str(),
        "mea"_a = mea(),
        "relative_norm"_a = relative_norm(),
        "absolute_norm"_a = absolute_norm(),
        "mark_price"_a = mark_price());
  }

 private:
  const FeedUpdate* upd_;
  int64_t timestamp_ = 0;
};

class PyIndexView {
 public:
  explicit PyIndexView(const FeedUpdate* upd) : upd_(upd), timestamp_(upd->timestamp()) {}

  // TODO(daniel): move to base class
  auto timestamp() const -> int64_t { return timestamp_; }
  auto exchange() const -> int { return static_cast<int>(upd_->exchange()); }
  auto exchange_str() const -> std::string { return ExchangeType_Name(exchange()); }
  auto market() const -> int { return static_cast<int>(upd_->market()); }
  auto market_str() const -> std::string { return MarketType_Name(market()); }
  auto mea() const -> std::string { return upd_->mea().String(); }
  auto relative_norm() const -> const std::string& { return upd_->product().relative_norm(); }
  auto absolute_norm() const -> const std::string& { return upd_->product().absolute_norm(); }
  auto feed_native_symbol() const -> const std::string& {
    return upd_->product().feed_native_symbol();
  }

  auto price() const -> double { return upd_->index().price; }

  auto as_dict() -> py::dict {
    using namespace coin2::exchange::feed;
    using namespace pybind11::literals;
    return py::dict(
        "type"_a = FeedType_Name(upd_->feed_type()),
        "timestamp"_a = timestamp(),
        "market"_a = market_str(),
        "exchange"_a = exchange_str(),
        "mea"_a = mea(),
        "relative_norm"_a = relative_norm(),
        "absolute_norm"_a = absolute_norm(),
        "price"_a = price());
  }

 private:
  const FeedUpdate* upd_;
  int64_t timestamp_ = 0;
};

class PyFundingRateView {
 public:
  explicit PyFundingRateView(const FeedUpdate* upd) : upd_(upd), timestamp_(upd->timestamp()) {}

  // TODO(daniel): move to base class
  auto timestamp() const -> int64_t { return timestamp_; }
  auto exchange() const -> int { return static_cast<int>(upd_->exchange()); }
  auto exchange_str() const -> std::string { return ExchangeType_Name(exchange()); }
  auto market() const -> int { return static_cast<int>(upd_->market()); }
  auto market_str() const -> std::string { return MarketType_Name(market()); }
  auto mea() const -> std::string { return upd_->mea().String(); }
  auto relative_norm() const -> const std::string& { return upd_->product().relative_norm(); }
  auto absolute_norm() const -> const std::string& { return upd_->product().absolute_norm(); }
  auto feed_native_symbol() const -> const std::string& {
    return upd_->product().feed_native_symbol();
  }

  auto funding_rate() const -> double { return upd_->funding_rate().funding_rate; }
  auto estimated_rate() const -> double { return upd_->funding_rate().estimated_rate; }
  auto funding_time() const -> int64_t { return upd_->funding_rate().funding_time; }

  auto as_dict() -> py::dict {
    using namespace coin2::exchange::feed;
    using namespace pybind11::literals;
    return py::dict(
        "type"_a = FeedType_Name(upd_->feed_type()),
        "timestamp"_a = timestamp(),
        "market"_a = market_str(),
        "exchange"_a = exchange_str(),
        "mea"_a = mea(),
        "relative_norm"_a = relative_norm(),
        "absolute_norm"_a = absolute_norm(),
        "funding_rate"_a = funding_rate(),
        "estimated_rate"_a = estimated_rate(),
        "funding_time"_a = funding_time());
  }

 private:
  const FeedUpdate* upd_;
  int64_t timestamp_ = 0;
};

class PyIvGreeksView {
 public:
  explicit PyIvGreeksView(const FeedUpdate* upd) : upd_(upd), timestamp_(upd->timestamp()) {}

  // TODO(daniel): move to base class
  auto timestamp() const -> int64_t { return timestamp_; }
  auto exchange() const -> int { return static_cast<int>(upd_->exchange()); }
  auto exchange_str() const -> std::string { return ExchangeType_Name(exchange()); }
  auto market() const -> int { return static_cast<int>(upd_->market()); }
  auto market_str() const -> std::string { return MarketType_Name(market()); }
  auto mea() const -> std::string { return upd_->mea().String(); }
  auto relative_norm() const -> const std::string& { return upd_->product().relative_norm(); }
  auto absolute_norm() const -> const std::string& { return upd_->product().absolute_norm(); }
  auto feed_native_symbol() const -> const std::string& {
    return upd_->product().feed_native_symbol();
  }

  auto ask_iv() const -> double { return upd_->iv_greeks().ask_iv; }
  auto bid_iv() const -> double { return upd_->iv_greeks().bid_iv; }
  auto mark_iv() const -> double { return upd_->iv_greeks().mark_iv; }
  auto delta() const -> double { return upd_->iv_greeks().delta; }
  auto gamma() const -> double { return upd_->iv_greeks().gamma; }
  auto rho() const -> double { return upd_->iv_greeks().rho; }
  auto theta() const -> double { return upd_->iv_greeks().theta; }
  auto vega() const -> double { return upd_->iv_greeks().vega; }

  auto as_dict() -> py::dict {
    using namespace coin2::exchange::feed;
    using namespace pybind11::literals;
    return py::dict(
        "type"_a = FeedType_Name(upd_->feed_type()),
        "timestamp"_a = timestamp(),
        "market"_a = market_str(),
        "exchange"_a = exchange_str(),
        "mea"_a = mea(),
        "relative_norm"_a = relative_norm(),
        "absolute_norm"_a = absolute_norm(),
        "ask_iv "_a = ask_iv(),
        "bid_iv"_a = bid_iv(),
        "mark_iv"_a = mark_iv(),
        "delta"_a = delta(),
        "gamma"_a = gamma(),
        "rho"_a = rho(),
        "theta"_a = theta(),
        "vega"_a = vega());
  }

 private:
  const FeedUpdate* upd_;
  int64_t timestamp_ = 0;
};

class PyNavView {
  class PyUnderlyingAsset {
   public:
    PyUnderlyingAsset(const UnderlyingAsset* underlying_asset)
        : underlying_asset_{underlying_asset} {}

    auto symbol_or_currency() const -> std::string { return underlying_asset_->symbol_or_currency; }
    auto amount() const -> double { return underlying_asset_->amount; }

   private:
    const UnderlyingAsset* underlying_asset_;
  };

 public:
  explicit PyNavView(const FeedUpdate* upd) : upd_(upd), timestamp_(upd->timestamp()) {}

  // TODO(daniel): move to base class
  auto timestamp() const -> int64_t { return timestamp_; }
  auto exchange() const -> int { return static_cast<int>(upd_->exchange()); }
  auto exchange_str() const -> std::string { return ExchangeType_Name(exchange()); }
  auto market() const -> int { return static_cast<int>(upd_->market()); }
  auto market_str() const -> std::string { return MarketType_Name(market()); }
  auto mea() const -> std::string { return upd_->mea().String(); }
  auto relative_norm() const -> const std::string& { return upd_->product().relative_norm(); }
  auto absolute_norm() const -> const std::string& { return upd_->product().absolute_norm(); }
  auto feed_native_symbol() const -> const std::string& {
    return upd_->product().feed_native_symbol();
  }

  auto actual_leverage() const -> double { return upd_->nav().actual_leverage; }
  auto nav() const -> double { return upd_->nav().actual_leverage; }
  auto outstanding() const -> double { return upd_->nav().actual_leverage; }
  auto basket() const -> std::vector<PyUnderlyingAsset> {
    if (0 == upd_->nav().basket.size()) {
      return {};
    }
    std::vector<PyUnderlyingAsset> ret;
    for (const auto& underlying_asset : upd_->nav().basket) {
      ret.emplace_back(PyUnderlyingAsset(&underlying_asset));
    }
    return ret;
  }

  auto as_dict() -> py::dict {
    using namespace coin2::exchange::feed;
    using namespace pybind11::literals;
    return py::dict(
        "type"_a = FeedType_Name(upd_->feed_type()),
        "timestamp"_a = timestamp(),
        "market"_a = market_str(),
        "exchange"_a = exchange_str(),
        "mea"_a = mea(),
        "relative_norm"_a = relative_norm(),
        "absolute_norm"_a = absolute_norm(),
        "actual_leverage"_a = actual_leverage(),
        "nav"_a = nav(),
        "outstanding"_a = outstanding(),
        "basket"_a = basket());
  }

 private:
  const FeedUpdate* upd_;
  int64_t timestamp_ = 0;
};

class PyKlineView {
 public:
  PyKlineView(const Kline* kline) : kline_{kline} {}

  auto timestamp() const -> int64_t { return kline_->timestamp; }
  auto interval() const -> std::string {
    return fastfeed::proto::KlineInterval_Name(kline_->interval);
  }
  auto open_timestamp() const -> int64_t { return kline_->open_timestamp; }
  auto close_timestamp() const -> int64_t { return kline_->close_timestamp; }
  auto open() const -> double { return kline_->open; }
  auto close() const -> double { return kline_->close; }
  auto high() const -> double { return kline_->high; }
  auto low() const -> double { return kline_->low; }
  auto volume() const -> double { return kline_->volume; }
  auto turnover() const -> double { return kline_->turnover; }
  auto buy_volume() const -> double { return kline_->buy_volume; }
  auto buy_turnover() const -> double { return kline_->buy_turnover; }

 private:
  const Kline* kline_;
};

class PyKlinesView {
 public:
  explicit PyKlinesView(const FeedUpdate* upd) : upd_(upd), timestamp_(upd->timestamp()) {}

  // TODO(daniel): move to base class
  auto timestamp() const -> int64_t { return timestamp_; }
  auto exchange() const -> int { return static_cast<int>(upd_->exchange()); }
  auto exchange_str() const -> std::string { return ExchangeType_Name(exchange()); }
  auto market() const -> int { return static_cast<int>(upd_->market()); }
  auto market_str() const -> std::string { return MarketType_Name(market()); }
  auto mea() const -> std::string { return upd_->mea().String(); }
  auto relative_norm() const -> const std::string& { return upd_->product().relative_norm(); }
  auto absolute_norm() const -> const std::string& { return upd_->product().absolute_norm(); }
  auto feed_native_symbol() const -> const std::string& {
    return upd_->product().feed_native_symbol();
  }

  auto size() const -> uint32_t { return upd_->klines().Size(); }
  auto at(unsigned int idx) -> std::optional<PyKlineView> {
    auto kline = upd_->klines().Get(idx);
    if (!kline) {
      return {};
    }
    return PyKlineView(kline.value());
  }
  auto klines() const -> std::vector<PyKlineView> {
    if (0 == size()) {
      return {};
    }
    std::vector<PyKlineView> ret;
    auto beg = upd_->klines().Begin();
    auto end = upd_->klines().End();
    while (beg != end) {
      ret.emplace_back(PyKlineView(&(*beg)));
    }
    return ret;
  }

  auto as_dict() -> py::dict {
    using namespace coin2::exchange::feed;
    using namespace pybind11::literals;
    return py::dict(
        "type"_a = FeedType_Name(upd_->feed_type()),
        "timestamp"_a = timestamp(),
        "market"_a = market_str(),
        "exchange"_a = exchange_str(),
        "mea"_a = mea(),
        "relative_norm"_a = relative_norm(),
        "absolute_norm"_a = absolute_norm(),
        "klines"_a = klines());
  }

 private:
  const FeedUpdate* upd_;
  int64_t timestamp_ = 0;
};

class PyWalletView {
 public:
  explicit PyWalletView(const FeedUpdate* upd) : upd_(upd), timestamp_(upd->timestamp()) {}

  // TODO(daniel): move to base class
  auto timestamp() const -> int64_t { return timestamp_; }
  auto exchange() const -> int { return static_cast<int>(upd_->exchange()); }
  auto exchange_str() const -> std::string { return ExchangeType_Name(exchange()); }
  auto market() const -> int { return static_cast<int>(upd_->market()); }
  auto market_str() const -> std::string { return MarketType_Name(market()); }
  auto mea() const -> std::string { return upd_->mea().String(); }
  auto relative_norm() const -> const std::string& { return upd_->product().relative_norm(); }
  auto absolute_norm() const -> const std::string& { return upd_->product().absolute_norm(); }
  auto feed_native_symbol() const -> const std::string& {
    return upd_->product().feed_native_symbol();
  }

  bool can_deposit() const { return upd_->wallet().can_deposit; }
  bool can_withdraw() const { return upd_->wallet().can_withdraw; }
  bool blockchain_delay() const { return upd_->wallet().blockchain_delay; }

  auto as_dict() -> py::dict {
    using namespace coin2::exchange::feed;
    using namespace pybind11::literals;
    return py::dict(
        "type"_a = FeedType_Name(upd_->feed_type()),
        "timestamp"_a = timestamp(),
        "market"_a = market_str(),
        "exchange"_a = exchange_str(),
        "mea"_a = mea(),
        "relative_norm"_a = relative_norm(),
        "absolute_norm"_a = absolute_norm(),
        "can_deposit"_a = can_deposit(),
        "can_withdraw"_a = can_withdraw(),
        "blockchain_delay"_a = blockchain_delay());
  }

 private:
  const FeedUpdate* upd_;
  int64_t timestamp_ = 0;
};

class PyMarketAnalysisView {
 public:
  explicit PyMarketAnalysisView(const FeedUpdate* upd) : upd_(upd), timestamp_(upd->timestamp()) {}

  // TODO(daniel): move to base class
  auto timestamp() const -> int64_t { return timestamp_; }
  auto exchange() const -> int { return static_cast<int>(upd_->exchange()); }
  auto exchange_str() const -> std::string { return ExchangeType_Name(exchange()); }
  auto market() const -> int { return static_cast<int>(upd_->market()); }
  auto market_str() const -> std::string { return MarketType_Name(market()); }
  auto mea() const -> std::string { return upd_->mea().String(); }
  auto relative_norm() const -> const std::string& { return upd_->product().relative_norm(); }
  auto absolute_norm() const -> const std::string& { return upd_->product().absolute_norm(); }
  auto feed_native_symbol() const -> const std::string& {
    return upd_->product().feed_native_symbol();
  }

  auto total_supply() const -> double { return upd_->market_analysis().total_supply; }
  auto max_supply() const -> double { return upd_->market_analysis().max_supply; }
  auto circulating_supply() const -> double { return upd_->market_analysis().circulating_supply; }
  auto market_cap_in_usd() const -> double { return upd_->market_analysis().market_cap_in_usd; }
  auto tvl_in_usd() const -> double { return upd_->market_analysis().tvl_in_usd; }

  auto as_dict() -> py::dict {
    using namespace coin2::exchange::feed;
    using namespace pybind11::literals;
    return py::dict(
        "type"_a = FeedType_Name(upd_->feed_type()),
        "timestamp"_a = timestamp(),
        "market"_a = market_str(),
        "exchange"_a = exchange_str(),
        "mea"_a = mea(),
        "relative_norm"_a = relative_norm(),
        "absolute_norm"_a = absolute_norm(),
        "total_supply"_a = total_supply(),
        "max_supply"_a = max_supply(),
        "circulating_supply"_a = circulating_supply(),
        "market_cap_in_usd"_a = market_cap_in_usd(),
        "tvl_in_usd"_a = tvl_in_usd());
  }

 private:
  const FeedUpdate* upd_;
  int64_t timestamp_ = 0;
};

class PyFeedUpdate {
 public:
  PyFeedUpdate() = default;
  explicit PyFeedUpdate(const FeedUpdate& upd) : upd_(&upd) {}
  int64_t timestamp() const { return upd_->timestamp(); }
  const std::string& system_name() const { return upd_->system_name(); }
  std::string mea() const { return upd_->mea().String(); }
  int exchange() const { return static_cast<int>(upd_->exchange()); }
  int market() const { return static_cast<int>(upd_->market()); }
  const std::string& feed_native_symbol() const { return upd_->product().feed_native_symbol(); }
  const std::string& absolute_norm() const { return upd_->product().absolute_norm(); }
  const std::string& relative_norm() const { return upd_->product().relative_norm(); }

  // Temporary helpers
  double Ask0p() const { return upd_->book().Ask0()->price; }
  double Bid0p() const { return upd_->book().Bid0()->price; }

  std::string AsFeedMessage() const;
  const FeedUpdate* upd_{};  // does not own
};

inline std::string PyFeedUpdate::AsFeedMessage() const {
  fastfeature::FeedMessage msg;
  std::string str;
  ::coin2::base::ConvertFeedMessage(*upd_, 10, &msg);
  msg.SerializeToString(&str);
  return str;
}

inline void AddPyFeedUpdate(py::module* m) {
  py::class_<PyFeedUpdate>(*m, "PyFeedUpdate")
      .def(py::init<>())
      .def_property("timestamp", &PyFeedUpdate::timestamp, nullptr)
      .def_property("name", &PyFeedUpdate::system_name, nullptr)
      .def_property("mea", &PyFeedUpdate::mea, nullptr)
      .def_property("market", &PyFeedUpdate::market, nullptr)
      .def_property("exchange", &PyFeedUpdate::exchange, nullptr)
      .def_property("native", &PyFeedUpdate::feed_native_symbol, nullptr)
      .def_property("rel_norm", &PyFeedUpdate::relative_norm, nullptr)
      .def_property("abs_norm", &PyFeedUpdate::absolute_norm, nullptr)
      .def_property("ask0p", &PyFeedUpdate::Ask0p, nullptr)
      .def_property("bid0p", &PyFeedUpdate::Bid0p, nullptr)
      .def("AsFeedMessage", [](const PyFeedUpdate& upd) {
        std::string out;
        out = upd.AsFeedMessage();
        return py::bytes(out);
      });

  py::class_<PyBookLevel>(*m, "_BookLevel")
      .def_readonly("price", &PyBookLevel::price)
      .def_readonly("qty", &PyBookLevel::qty)
      .def_readonly("count", &PyBookLevel::count)
      .def("as_dict", &PyBookLevel::as_dict);

  py::class_<PyBookView, std::shared_ptr<PyBookView>>(*m, "_BookView")
      .def_property_readonly("mea", &PyBookView::mea)
      .def_property_readonly("exchange", &PyBookView::exchange)
      .def_property_readonly("exchange_str", &PyBookView::exchange_str)
      .def_property_readonly("market", &PyBookView::market)
      .def_property_readonly("market_str", &PyBookView::market_str)
      .def_property_readonly("timestamp", &PyBookView::timestamp)
      .def_property_readonly("relative_norm", &PyBookView::relative_norm)
      .def_property_readonly("absolute_norm", &PyBookView::absolute_norm)
      .def_property_readonly("native_symbol", &PyBookView::feed_native_symbol)
      .def("has_ask", &PyBookView::has_ask)
      .def("has_bid", &PyBookView::has_bid)
      .def("depth_ask", &PyBookView::depth_ask)
      .def("depth_bid", &PyBookView::depth_bid)
      .def("ask0", &PyBookView::ask0)
      .def("bid0", &PyBookView::bid0)
      .def("set_timestamp", &PyBookView::set_timestamp)
      .def("get_ask_array", &PyBookView::get_ask_array)
      .def("get_bid_array", &PyBookView::get_bid_array)
      .def("take_copy", &PyBookView::TakeCopy)
      .def("as_dict", &PyBookView::as_dict);

  py::class_<PyBookViewPickleable, std::shared_ptr<PyBookViewPickleable>>(*m, "_BookViewPickleable")
      .def_property_readonly("type", &PyBookViewPickleable::type_str)
      .def_property_readonly("mea", &PyBookViewPickleable::mea_str)
      .def_property_readonly("exchange", &PyBookViewPickleable::exchange_str)
      .def_property_readonly("market", &PyBookViewPickleable::market_str)
      .def_property_readonly("exchange_str", &PyBookViewPickleable::exchange_str)
      .def_property_readonly("market_str", &PyBookViewPickleable::market_str)
      .def_property_readonly("relative_norm", &PyBookViewPickleable::relative_norm)
      .def_property_readonly("absolute_norm", &PyBookViewPickleable::absolute_norm)
      .def_property_readonly("native_symbol", &PyBookViewPickleable::native_symbol)
      .def_property_readonly("timestamp", &PyBookViewPickleable::timestamp)
      .def("has_ask", &PyBookViewPickleable::has_ask)
      .def("has_bid", &PyBookViewPickleable::has_bid)
      .def("depth_ask", &PyBookViewPickleable::depth_ask)
      .def("depth_bid", &PyBookViewPickleable::depth_bid)
      .def("ask0", &PyBookViewPickleable::ask0)
      .def("bid0", &PyBookViewPickleable::bid0)
      .def("get_ask_array", &PyBookViewPickleable::get_ask_array)
      .def("get_bid_array", &PyBookViewPickleable::get_bid_array)
      .def("as_dict", &PyBookViewPickleable::as_dict)
      .def(py::pickle(
          [](const PyBookViewPickleable& p) {  // __getstate__
            /* Return a tuple that fully encodes the state of the object */
            return py::make_tuple(
                p.type_str(),
                p.mea_str(),
                p.market_str(),
                p.exchange_str(),
                p.relative_norm(),
                p.absolute_norm(),
                p.native_symbol(),
                p.timestamp(),
                p.asks(),
                p.bids());
          },
          [](py::tuple t) {  // __setstate__
            if (t.size() != 10) {
              CHECK(false) << "invalid parameters!";
            }
            /* Create a new C++ instance */
            PyBookViewPickleable p(
                t[0].cast<std::string>(),
                t[1].cast<std::string>(),
                t[2].cast<std::string>(),
                t[3].cast<std::string>(),
                t[4].cast<std::string>(),
                t[5].cast<std::string>(),
                t[6].cast<std::string>(),
                t[7].cast<int64_t>(),
                t[8].cast<py::list>(),
                t[9].cast<py::list>());
            return p;
          }));

  py::class_<PyTradeView, std::shared_ptr<PyTradeView>>(*m, "_TradeView")
      .def_property_readonly("mea", &PyTradeView::mea)
      .def_property_readonly("exchange", &PyTradeView::exchange)
      .def_property_readonly("exchange_str", &PyTradeView::exchange_str)
      .def_property_readonly("market", &PyTradeView::market)
      .def_property_readonly("market_str", &PyTradeView::market_str)
      .def_property_readonly("relative_norm", &PyTradeView::relative_norm)
      .def_property_readonly("absolute_norm", &PyTradeView::absolute_norm)
      .def_property_readonly("native_symbol", &PyTradeView::feed_native_symbol)
      .def_property_readonly("timestamp", &PyTradeView::timestamp)
      .def_property_readonly("exchange_publish_timestamp", &PyTradeView::exchange_publish_timestamp)
      .def_property_readonly("price", &PyTradeView::price)
      .def_property_readonly("qty", &PyTradeView::qty)
      .def_property_readonly("side", &PyTradeView::side)
      .def_property_readonly("has_more_trade", &PyTradeView::has_more_trade)
      .def("set_timestamp", &PyTradeView::set_timestamp)
      .def("take_copy", &PyTradeView::TakeCopy)
      .def("as_dict", &PyTradeView::as_dict);

  py::class_<PyTradeViewPickleable, std::shared_ptr<PyTradeViewPickleable>>(
      *m,
      "_TradeViewPickleable")
      .def_property_readonly("type", &PyTradeViewPickleable::type_str)
      .def_property_readonly("mea", &PyTradeViewPickleable::mea_str)
      .def_property_readonly("market_str", &PyTradeViewPickleable::market_str)
      .def_property_readonly("exchange_str", &PyTradeViewPickleable::exchange_str)
      .def_property_readonly("relative_norm", &PyTradeViewPickleable::relative_norm)
      .def_property_readonly("absolute_norm", &PyTradeViewPickleable::absolute_norm)
      .def_property_readonly("native_symbol", &PyTradeViewPickleable::native_symbol)
      .def_property_readonly("timestamp", &PyTradeViewPickleable::timestamp)
      .def_property_readonly("price", &PyTradeViewPickleable::price)
      .def_property_readonly("qty", &PyTradeViewPickleable::qty)
      .def_property_readonly("side", &PyTradeViewPickleable::side)
      .def_property_readonly("has_more_trade", &PyTradeViewPickleable::has_more_trade)
      .def("as_dict", &PyTradeViewPickleable::as_dict)
      .def(py::pickle(
          [](const PyTradeViewPickleable& p) {  // __getstate__
            /* Return a tuple that fully encodes the state of the object */
            return py::make_tuple(
                p.type_str(),
                p.mea_str(),
                p.market_str(),
                p.exchange_str(),
                p.relative_norm(),
                p.absolute_norm(),
                p.native_symbol(),
                p.timestamp(),
                p.price(),
                p.qty(),
                p.side(),
                p.has_more_trade());
          },
          [](py::tuple t) {  // __setstate__
            if (t.size() != 12) {
              CHECK(false) << "invalid parameters!";
            }
            /* Create a new C++ instance */
            PyTradeViewPickleable p(
                t[0].cast<std::string>(),
                t[1].cast<std::string>(),
                t[2].cast<std::string>(),
                t[3].cast<std::string>(),
                t[4].cast<std::string>(),
                t[5].cast<std::string>(),
                t[6].cast<std::string>(),
                t[7].cast<int64_t>(),
                t[8].cast<double>(),
                t[9].cast<double>(),
                t[10].cast<int>(),
                t[11].cast<bool>());
            return p;
          }));

  py::class_<PyLiquidationView, std::shared_ptr<PyLiquidationView>>(*m, "_LiquidationView")
      .def_property_readonly("mea", &PyLiquidationView::mea)
      .def_property_readonly("exchange", &PyLiquidationView::exchange)
      .def_property_readonly("exchange_str", &PyLiquidationView::exchange_str)
      .def_property_readonly("market", &PyLiquidationView::market)
      .def_property_readonly("market_str", &PyLiquidationView::market_str)
      .def_property_readonly("relative_norm", &PyLiquidationView::relative_norm)
      .def_property_readonly("absolute_norm", &PyLiquidationView::absolute_norm)
      .def_property_readonly("native_symbol", &PyLiquidationView::feed_native_symbol)
      .def_property_readonly("timestamp", &PyLiquidationView::timestamp)
      .def_property_readonly("order_price", &PyLiquidationView::order_price)
      .def_property_readonly("order_qty", &PyLiquidationView::order_qty)
      .def_property_readonly("avg_price", &PyLiquidationView::avg_price)
      .def_property_readonly("fill_qty", &PyLiquidationView::fill_qty)
      .def_property_readonly("order_side", &PyLiquidationView::order_side)
      .def_property_readonly("order_duration", &PyLiquidationView::order_duration)
      .def_property_readonly("order_type", &PyLiquidationView::order_type)
      .def_property_readonly("order_id", &PyLiquidationView::order_id)
      .def("as_dict", &PyLiquidationView::as_dict);

  py::class_<PyOpenInterestView, std::shared_ptr<PyOpenInterestView>>(*m, "_OpenInterestView")
      .def_property_readonly("mea", &PyOpenInterestView::mea)
      .def_property_readonly("exchange", &PyOpenInterestView::exchange)
      .def_property_readonly("exchange_str", &PyOpenInterestView::exchange_str)
      .def_property_readonly("market", &PyOpenInterestView::market)
      .def_property_readonly("market_str", &PyOpenInterestView::market_str)
      .def_property_readonly("relative_norm", &PyOpenInterestView::relative_norm)
      .def_property_readonly("absolute_norm", &PyOpenInterestView::absolute_norm)
      .def_property_readonly("native_symbol", &PyOpenInterestView::feed_native_symbol)
      .def_property_readonly("timestamp", &PyOpenInterestView::timestamp)
      .def_property_readonly("open_interest_qty", &PyOpenInterestView::open_interest_qty)
      .def("as_dict", &PyOpenInterestView::as_dict);

  py::class_<PyMarkPriceView, std::shared_ptr<PyMarkPriceView>>(*m, "_MarkPriceView")
      .def_property_readonly("mea", &PyMarkPriceView::mea)
      .def_property_readonly("exchange", &PyMarkPriceView::exchange)
      .def_property_readonly("exchange_str", &PyMarkPriceView::exchange_str)
      .def_property_readonly("market", &PyMarkPriceView::market)
      .def_property_readonly("market_str", &PyMarkPriceView::market_str)
      .def_property_readonly("relative_norm", &PyMarkPriceView::relative_norm)
      .def_property_readonly("absolute_norm", &PyMarkPriceView::absolute_norm)
      .def_property_readonly("native_symbol", &PyMarkPriceView::feed_native_symbol)
      .def_property_readonly("timestamp", &PyMarkPriceView::timestamp)
      .def_property_readonly("mark_price", &PyMarkPriceView::mark_price)
      .def("as_dict", &PyMarkPriceView::as_dict);

  py::class_<PyIndexView, std::shared_ptr<PyIndexView>>(*m, "_IndexView")
      .def_property_readonly("mea", &PyIndexView::mea)
      .def_property_readonly("exchange", &PyIndexView::exchange)
      .def_property_readonly("exchange_str", &PyIndexView::exchange_str)
      .def_property_readonly("market", &PyIndexView::market)
      .def_property_readonly("market_str", &PyIndexView::market_str)
      .def_property_readonly("relative_norm", &PyIndexView::relative_norm)
      .def_property_readonly("absolute_norm", &PyIndexView::absolute_norm)
      .def_property_readonly("native_symbol", &PyIndexView::feed_native_symbol)
      .def_property_readonly("timestamp", &PyIndexView::timestamp)
      .def_property_readonly("price", &PyIndexView::price)
      .def("as_dict", &PyIndexView::as_dict);

  py::class_<PyFundingRateView, std::shared_ptr<PyFundingRateView>>(*m, "_FundingRateView")
      .def_property_readonly("mea", &PyFundingRateView::mea)
      .def_property_readonly("exchange", &PyFundingRateView::exchange)
      .def_property_readonly("exchange_str", &PyFundingRateView::exchange_str)
      .def_property_readonly("market", &PyFundingRateView::market)
      .def_property_readonly("market_str", &PyFundingRateView::market_str)
      .def_property_readonly("relative_norm", &PyFundingRateView::relative_norm)
      .def_property_readonly("absolute_norm", &PyFundingRateView::absolute_norm)
      .def_property_readonly("native_symbol", &PyFundingRateView::feed_native_symbol)
      .def_property_readonly("timestamp", &PyFundingRateView::timestamp)
      .def_property_readonly("funding_rate", &PyFundingRateView::funding_rate)
      .def_property_readonly("estimated_rate", &PyFundingRateView::estimated_rate)
      .def_property_readonly("funding_time", &PyFundingRateView::funding_time)
      .def("as_dict", &PyFundingRateView::as_dict);

  py::class_<PyIvGreeksView, std::shared_ptr<PyIvGreeksView>>(*m, "_IvGreeksView")
      .def_property_readonly("mea", &PyIvGreeksView::mea)
      .def_property_readonly("exchange", &PyIvGreeksView::exchange)
      .def_property_readonly("exchange_str", &PyIvGreeksView::exchange_str)
      .def_property_readonly("market", &PyIvGreeksView::market)
      .def_property_readonly("market_str", &PyIvGreeksView::market_str)
      .def_property_readonly("relative_norm", &PyIvGreeksView::relative_norm)
      .def_property_readonly("absolute_norm", &PyIvGreeksView::absolute_norm)
      .def_property_readonly("native_symbol", &PyIvGreeksView::feed_native_symbol)
      .def_property_readonly("timestamp", &PyIvGreeksView::timestamp)
      .def_property_readonly("ask_iv", &PyIvGreeksView::ask_iv)
      .def_property_readonly("bid_iv", &PyIvGreeksView::bid_iv)
      .def_property_readonly("mark_iv", &PyIvGreeksView::mark_iv)
      .def_property_readonly("delta", &PyIvGreeksView::delta)
      .def_property_readonly("gamma", &PyIvGreeksView::gamma)
      .def_property_readonly("rho", &PyIvGreeksView::rho)
      .def_property_readonly("theta", &PyIvGreeksView::theta)
      .def_property_readonly("vega", &PyIvGreeksView::vega)
      .def("as_dict", &PyIvGreeksView::as_dict);

  py::class_<PyNavView, std::shared_ptr<PyNavView>>(*m, "_NavView")
      .def_property_readonly("mea", &PyNavView::mea)
      .def_property_readonly("exchange", &PyNavView::exchange)
      .def_property_readonly("exchange_str", &PyNavView::exchange_str)
      .def_property_readonly("market", &PyNavView::market)
      .def_property_readonly("market_str", &PyNavView::market_str)
      .def_property_readonly("relative_norm", &PyNavView::relative_norm)
      .def_property_readonly("absolute_norm", &PyNavView::absolute_norm)
      .def_property_readonly("native_symbol", &PyNavView::feed_native_symbol)
      .def_property_readonly("timestamp", &PyNavView::timestamp)
      .def_property_readonly("actual_leverage", &PyNavView::actual_leverage)
      .def_property_readonly("nav", &PyNavView::nav)
      .def_property_readonly("outstanding", &PyNavView::outstanding)
      .def_property_readonly("basket", &PyNavView::basket)
      .def("as_dict", &PyNavView::as_dict);

  py::class_<PyKlineView, std::shared_ptr<PyKlineView>>(*m, "_KlineView")
      .def_property_readonly("open_timestamp", &PyKlineView::open_timestamp)
      .def_property_readonly("close_timestamp", &PyKlineView::close_timestamp)
      .def_property_readonly("open", &PyKlineView::open)
      .def_property_readonly("close", &PyKlineView::close)
      .def_property_readonly("high", &PyKlineView::high)
      .def_property_readonly("low", &PyKlineView::low)
      .def_property_readonly("volume", &PyKlineView::volume)
      .def_property_readonly("turnover", &PyKlineView::turnover)
      .def_property_readonly("timestamp", &PyKlineView::timestamp)
      .def_property_readonly("buy_volume", &PyKlineView::buy_volume)
      .def_property_readonly("buy_turnover", &PyKlineView::buy_turnover)
      .def("interval", &PyKlineView::interval);
      
  py::class_<PyKlinesView, std::shared_ptr<PyKlinesView>>(*m, "_KlinesView")
      .def_property_readonly("mea", &PyKlinesView::mea)
      .def_property_readonly("exchange", &PyKlinesView::exchange)
      .def_property_readonly("exchange_str", &PyKlinesView::exchange_str)
      .def_property_readonly("market", &PyKlinesView::market)
      .def_property_readonly("market_str", &PyKlinesView::market_str)
      .def_property_readonly("relative_norm", &PyKlinesView::relative_norm)
      .def_property_readonly("absolute_norm", &PyKlinesView::absolute_norm)
      .def_property_readonly("native_symbol", &PyKlinesView::feed_native_symbol)
      .def_property_readonly("timestamp", &PyKlinesView::timestamp)
      .def_property_readonly("size", &PyKlinesView::size)
      .def("at", &PyKlinesView::at)
      .def("klines", &PyKlinesView::klines)
      .def("as_dict", &PyKlinesView::as_dict);

  py::class_<PyWalletView, std::shared_ptr<PyWalletView>>(*m, "_WalletView")
      .def_property_readonly("mea", &PyWalletView::mea)
      .def_property_readonly("exchange", &PyWalletView::exchange)
      .def_property_readonly("exchange_str", &PyWalletView::exchange_str)
      .def_property_readonly("market", &PyWalletView::market)
      .def_property_readonly("market_str", &PyWalletView::market_str)
      .def_property_readonly("relative_norm", &PyWalletView::relative_norm)
      .def_property_readonly("absolute_norm", &PyWalletView::absolute_norm)
      .def_property_readonly("native_symbol", &PyWalletView::feed_native_symbol)
      .def_property_readonly("timestamp", &PyWalletView::timestamp)
      .def_property_readonly("can_deposit", &PyWalletView::can_deposit)
      .def_property_readonly("can_withdraw", &PyWalletView::can_withdraw)
      .def_property_readonly("blockchain_delay", &PyWalletView::blockchain_delay)
      .def("as_dict", &PyWalletView::as_dict);

  py::class_<PyMarketAnalysisView, std::shared_ptr<PyMarketAnalysisView>>(*m, "_MarketAnalysisView")
      .def_property_readonly("mea", &PyMarketAnalysisView::mea)
      .def_property_readonly("exchange", &PyMarketAnalysisView::exchange)
      .def_property_readonly("exchange_str", &PyMarketAnalysisView::exchange_str)
      .def_property_readonly("market", &PyMarketAnalysisView::market)
      .def_property_readonly("market_str", &PyMarketAnalysisView::market_str)
      .def_property_readonly("relative_norm", &PyMarketAnalysisView::relative_norm)
      .def_property_readonly("absolute_norm", &PyMarketAnalysisView::absolute_norm)
      .def_property_readonly("native_symbol", &PyMarketAnalysisView::feed_native_symbol)
      .def_property_readonly("timestamp", &PyMarketAnalysisView::timestamp)
      .def_property_readonly("total_supply", &PyMarketAnalysisView::total_supply)
      .def_property_readonly("max_supply", &PyMarketAnalysisView::max_supply)
      .def_property_readonly("circulating_supply", &PyMarketAnalysisView::circulating_supply)
      .def_property_readonly("market_cap_in_usd", &PyMarketAnalysisView::market_cap_in_usd)
      .def_property_readonly("tvl_in_usd", &PyMarketAnalysisView::tvl_in_usd)
      .def("as_dict", &PyMarketAnalysisView::as_dict);
}

}  // namespace impl
using impl::PyFeedUpdate;
}  // namespace coin2::exchange::driver
